#include "ArrayObject.hpp"

namespace gl
{

ArrayObject::ArrayObject() :
	mRenderMode(ArrayObject::RenderMode::VERTICES),
	mPositionAttributeActivated(false), 
	mNormalAttributeActivated(false), 
	mIndexDataSet(false),
	mTexCoordAttributeActivated(false),
	mVao(0),
	mVbo(0),
	mEbo(0),
	mNbo(0),
	mTbo(0),
	mDrawByteCount(0)
{
	glGenVertexArrays(1, &mVao);
}

bool ArrayObject::setVertices(const std::vector<float> &vertices, ArrayObject::StoreType storeType)
{
	glBindVertexArray(mVao);

	unsigned int glStoreType = GL_STATIC_DRAW;
	switch(storeType)
	{
		case StoreType::STREAM:
			glStoreType = GL_STREAM_DRAW;
			break;
		case StoreType::DYNAMIC:
			glStoreType = GL_DYNAMIC_DRAW;
			break;
	}

	if(!mPositionAttributeActivated)
	{
		glGenBuffers(1, &mVbo);
		glBindBuffer(GL_ARRAY_BUFFER, mVbo);

		glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), glStoreType);
		GLenum glError = glGetError();
		switch(glError)
		{
			case GL_OUT_OF_MEMORY:
				// Unable to allocate a buffer of vertices.size() * sizeof(float) bytes.
				glBindVertexArray(0);
				glBindBuffer(GL_ARRAY_BUFFER, 0);
				return false;
		}
		mDrawByteCount = vertices.size() / 3;

		glEnableVertexAttribArray(0);
		glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
		mPositionAttributeActivated = true;
		debugstring("Updated vertices, num vertices: %d\n", vertices.size() / 3);
	}
	else
	{
		glBindBuffer(GL_ARRAY_BUFFER, mVbo);

		glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), glStoreType);
		GLenum glError = glGetError();
		switch(glError)
		{
			case GL_OUT_OF_MEMORY:
				// Unable to allocate a buffer of vertices.size() * sizeof(float) bytes.
				glBindVertexArray(0);
				glBindBuffer(GL_ARRAY_BUFFER, 0);
				return false;
		}
		mDrawByteCount = vertices.size() / 3;
	}

	glBindVertexArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	return true;
}

bool ArrayObject::setIndices(const std::vector<unsigned int> &indices, ArrayObject::StoreType storeType)
{
	glBindVertexArray(mVao);

	unsigned int glStoreType = GL_STATIC_DRAW;
	switch(storeType)
	{
		case StoreType::STREAM:
			glStoreType = GL_STREAM_DRAW;
			break;
		case StoreType::DYNAMIC:
			glStoreType = GL_DYNAMIC_DRAW;
			break;
	}

	if(!mIndexDataSet)
	{
		glGenBuffers(1, &mEbo);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mEbo);

		glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), glStoreType);
		GLenum glError = glGetError();
		switch(glError)
		{
			case GL_OUT_OF_MEMORY:
				// Unable to allocate a buffer of vertices.size() * sizeof(float) bytes.
				glBindVertexArray(0);
				glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
				return false;
		}
		mIndexDataSet = true;
		mRenderMode = ArrayObject::RenderMode::INDICES;
		mDrawByteCount = indices.size();
	}
	else
	{
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mEbo);

		glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), glStoreType);
		GLenum glError = glGetError();
		switch(glError)
		{
			case GL_OUT_OF_MEMORY:
				// Unable to allocate a buffer of vertices.size() * sizeof(float) bytes.
				glBindVertexArray(0);
				glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
				return false;
		}
		mRenderMode = ArrayObject::RenderMode::INDICES;
		mDrawByteCount = indices.size();
	}

	glBindVertexArray(0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	return true;
}

bool ArrayObject::setNormals(const std::vector<float> &normals, ArrayObject::StoreType storeType)
{
	glBindVertexArray(mVao);

	unsigned int glStoreType = GL_STATIC_DRAW;
	switch(storeType)
	{
		case StoreType::STREAM:
			glStoreType = GL_STREAM_DRAW;
			break;
		case StoreType::DYNAMIC:
			glStoreType = GL_DYNAMIC_DRAW;
			break;
	}

	if(!mNormalAttributeActivated)
	{
		glGenBuffers(1, &mNbo);
		glBindBuffer(GL_ARRAY_BUFFER, mNbo);

		glBufferData(GL_ARRAY_BUFFER, normals.size() * sizeof(float), normals.data(), glStoreType);
		GLenum glError = glGetError();
		switch(glError)
		{
			case GL_OUT_OF_MEMORY:
				// Unable to allocate a buffer of normals.size() * sizeof(float) bytes.
				glBindVertexArray(0);
				glBindBuffer(GL_ARRAY_BUFFER, 0);
				return false;
		}

		glEnableVertexAttribArray(1);
		glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, 0);
		mNormalAttributeActivated = true;
	}
	else
	{
		glBindBuffer(GL_ARRAY_BUFFER, mNbo);

		glBufferData(GL_ARRAY_BUFFER, normals.size() * sizeof(float), normals.data(), glStoreType);
		GLenum glError = glGetError();
		switch(glError)
		{
			case GL_OUT_OF_MEMORY:
				// Unable to allocate a buffer of normals.size() * sizeof(float) bytes.
				glBindVertexArray(0);
				glBindBuffer(GL_ARRAY_BUFFER, 0);
				return false;
		}
	}

	glBindVertexArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	return true;
}

bool ArrayObject::setTexCoords(const std::vector<float> &texCoords, ArrayObject::StoreType storeType)
{
	glBindVertexArray(mVao);

	unsigned int glStoreType = GL_STATIC_DRAW;
	switch(storeType)
	{
		case StoreType::STREAM:
			glStoreType = GL_STREAM_DRAW;
			break;
		case StoreType::DYNAMIC:
			glStoreType = GL_DYNAMIC_DRAW;
			break;
	}

	if(!mTexCoordAttributeActivated)
	{
		glGenBuffers(1, &mTbo);
		glBindBuffer(GL_ARRAY_BUFFER, mTbo);

		glBufferData(GL_ARRAY_BUFFER, texCoords.size() * sizeof(float), texCoords.data(), glStoreType);
		GLenum glError = glGetError();
		switch(glError)
		{
			case GL_OUT_OF_MEMORY:
				// Unable to allocate a buffer of vertices.size() * sizeof(float) bytes.
				glBindVertexArray(0);
				glBindBuffer(GL_ARRAY_BUFFER, 0);
				return false;
		}

		glEnableVertexAttribArray(2);
		glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, 0);
		mTexCoordAttributeActivated = true;
	}
	else
	{
		glBindBuffer(GL_ARRAY_BUFFER, mTbo);

		glBufferData(GL_ARRAY_BUFFER, texCoords.size() * sizeof(float), texCoords.data(), glStoreType);
		GLenum glError = glGetError();
		switch(glError)
		{
			case GL_OUT_OF_MEMORY:
				// Unable to allocate a buffer of vertices.size() * sizeof(float) bytes.
				glBindVertexArray(0);
				glBindBuffer(GL_ARRAY_BUFFER, 0);
				return false;
		}
	}

	glBindVertexArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	return true;
}

bool ArrayObject::setSubVertices(const std::vector<float> &vertices, unsigned int offset)
{
	if(!mPositionAttributeActivated) return false;

	glBindVertexArray(mVao);

	glBindBuffer(GL_ARRAY_BUFFER, mVbo);
	glBufferSubData(GL_ARRAY_BUFFER, offset, vertices.size() * sizeof(float), vertices.data());
	GLenum glError = glGetError();
	switch(glError)
	{
		case GL_INVALID_VALUE:
			// Sub data exceeds glBufferData allocated size.
			// Use glBufferData (setVertices) instead to reallocate buffer.
			glBindVertexArray(0);
			glBindBuffer(GL_ARRAY_BUFFER, 0);
			return false;
	}

	glBindVertexArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	return true;
}

bool ArrayObject::setSubIndices(const std::vector<unsigned int> &indices, unsigned int offset)
{
	if(!mIndexDataSet) return false;

	glBindVertexArray(mVao);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mEbo);
	glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, indices.size() * sizeof(unsigned int), indices.data());
	GLenum glError = glGetError();
	switch(glError)
	{
		case GL_INVALID_VALUE:
			// Sub data exceeds glBufferData allocated size.
			// Use glBufferData (setIndices) instead to reallocate buffer.
			glBindVertexArray(0);
			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
			return false;
	}

	glBindVertexArray(0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	return true;
}

bool ArrayObject::setSubNormals(const std::vector<float> &normals, unsigned int offset)
{
	if(!mNormalAttributeActivated) return false;

	glBindVertexArray(mVao);

	glBindBuffer(GL_ARRAY_BUFFER, mNbo);
	glBufferSubData(GL_ARRAY_BUFFER, offset, normals.size() * sizeof(float), normals.data());
	GLenum glError = glGetError();
	switch(glError)
	{
		case GL_INVALID_VALUE:
			// Sub data exceeds glBufferData allocated size.
			// Use glBufferData (setNormals) instead to reallocate buffer.
			glBindVertexArray(0);
			glBindBuffer(GL_ARRAY_BUFFER, 0);
			return false;
	}

	glBindVertexArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	return true;
}

void ArrayObject::render()
{
	glBindVertexArray(mVao);

	switch(mRenderMode)
	{
		case RenderMode::VERTICES:
			glDrawArrays(GL_TRIANGLES, 0, mDrawByteCount);
			break;
		case RenderMode::INDICES:
			glDrawElements(GL_TRIANGLES, mDrawByteCount, GL_UNSIGNED_INT, 0);
			break;
	}

	glBindVertexArray(NULL);
}

bool ArrayObject::setVertices(const aiVector3D *vertices, unsigned int size, ArrayObject::StoreType storeType)
{
	glBindVertexArray(mVao);

	unsigned int glStoreType = GL_STATIC_DRAW;
	switch(storeType)
	{
		case StoreType::STREAM:
			glStoreType = GL_STREAM_DRAW;
			break;
		case StoreType::DYNAMIC:
			glStoreType = GL_DYNAMIC_DRAW;
			break;
	}

	if(!mPositionAttributeActivated)
	{
		glGenBuffers(1, &mVbo);
		glBindBuffer(GL_ARRAY_BUFFER, mVbo);

		glBufferData(GL_ARRAY_BUFFER, size * sizeof(float), &vertices[0], glStoreType);
		GLenum glError = glGetError();
		switch(glError)
		{
			case GL_OUT_OF_MEMORY:
				// Unable to allocate a buffer of vertices.size() * sizeof(float) bytes.
				glBindVertexArray(0);
				glBindBuffer(GL_ARRAY_BUFFER, 0);
				return false;
		}
		mDrawByteCount = size * sizeof(float);

		glEnableVertexAttribArray(0);
		glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
		mPositionAttributeActivated = true;
	}
	else
	{
		glBindBuffer(GL_ARRAY_BUFFER, mVbo);

		glBufferData(GL_ARRAY_BUFFER, size * sizeof(float), &vertices[0], glStoreType);
		GLenum glError = glGetError();
		switch(glError)
		{
			case GL_OUT_OF_MEMORY:
				// Unable to allocate a buffer of vertices.size() * sizeof(float) bytes.
				glBindVertexArray(0);
				glBindBuffer(GL_ARRAY_BUFFER, 0);
				return false;
		}
		mDrawByteCount = size * sizeof(float);
	}

	glBindVertexArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	return true;
}

bool ArrayObject::setNormals(const aiVector3D *normals, unsigned int size, ArrayObject::StoreType storeType)
{
	glBindVertexArray(mVao);

	unsigned int glStoreType = GL_STATIC_DRAW;
	switch(storeType)
	{
		case StoreType::STREAM:
			glStoreType = GL_STREAM_DRAW;
			break;
		case StoreType::DYNAMIC:
			glStoreType = GL_DYNAMIC_DRAW;
			break;
	}

	if(!mNormalAttributeActivated)
	{
		glGenBuffers(1, &mNbo);
		glBindBuffer(GL_ARRAY_BUFFER, mNbo);

		glBufferData(GL_ARRAY_BUFFER, size * sizeof(float), &normals[0], glStoreType);
		GLenum glError = glGetError();
		switch(glError)
		{
			case GL_OUT_OF_MEMORY:
				// Unable to allocate a buffer of normals.size() * sizeof(float) bytes.
				glBindVertexArray(0);
				glBindBuffer(GL_ARRAY_BUFFER, 0);
				return false;
		}

		glEnableVertexAttribArray(1);
		glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, 0);
		mNormalAttributeActivated = true;
	}
	else
	{
		glBindBuffer(GL_ARRAY_BUFFER, mNbo);

		glBufferData(GL_ARRAY_BUFFER, size * sizeof(float), &normals[0], glStoreType);
		GLenum glError = glGetError();
		switch(glError)
		{
		case GL_OUT_OF_MEMORY:
			// Unable to allocate a buffer of normals.size() * sizeof(float) bytes.
			glBindVertexArray(0);
			glBindBuffer(GL_ARRAY_BUFFER, 0);
			return false;
		}
	}

	glBindVertexArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	return true;
}

ArrayObject::~ArrayObject()
{
	glDeleteVertexArrays(1, &mVao);
	glDeleteBuffers(1, &mVbo);
	glDeleteBuffers(1, &mEbo);
	glDeleteBuffers(1, &mNbo);
	glDeleteBuffers(1, &mTbo);
}

}